package com.yibo.support.jwt;

import com.yibo.support.exception.YBException;

import javax.crypto.Cipher;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.HashMap;

/**
 * RSA加密
 *
 * @author zhanglei
 */
public class RSAUtils {

    /**
     * 通过getKeys方法获取默认的模
     */
    public static String modules = "109641337784430056962515853108430507407142452963568272172785106188018444534813236695317554221063218127119597190080692841231905976402385501560482246076569956855393678618107279442335873798544729682202898969388945467963383858914902936856235690940484051698978939588886790816654860784444445226065615857956716363081";

    /**
     * 通过getKeys方法获取map中的public公钥的值
     */
    public static String public_exponent = "65537";

    /**
     * 通过getKeys方法获取map中的private私钥的值  注意这三个值并不是随便设置的。
     */
    public static String private_exponent = "37661867284176716089234430705967431874354805699312585915646708126778327103280827387353537400622170170708399407099753684816403975475998327832057865384069188343953086159484722325836177188362850617668335679243310712059070889674878150516593654817726553526960017235752025488206982983422005725266634971961843131137";

    /**
     * 公钥加密
     *
     * @param data  加密数据
     * @return        加密返回密码
     * @throws Exception   异常
     */
    public static String encryptByPublicKey(String data) throws Exception{
        RSAPublicKey publicKey = getPublicKey(modules,public_exponent);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE,publicKey);
        //模长
        int keyLen = publicKey.getModulus().bitLength() / 8;
        //加密数据长度 <= 模长-11
        String[] datas = splitString(data,keyLen-11);
        StringBuilder mi = new StringBuilder();
        //如果明文长度大于模长-11，则要分组加密
        for(String s : datas){
            mi.append(bcdToStr(cipher.doFinal(s.getBytes())));
        }
        return mi.toString();
    }


    /**
     * 私钥解密
     *
     *
     * @param data
     * @return
     * @throws Exception
     */
    public static String decryptByPrivateKey(String data) throws Exception{
        RSAPrivateKey privateKey = getPrivateKey(modules,private_exponent);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE,privateKey);

        //模长
        int keyLen = privateKey.getModulus().bitLength() / 8;
        byte[] bytes = data.getBytes();
        byte[] bcd = ASCII_TO_BCD(bytes,bytes.length);

        //如果密文长度大于模长，则需要分组解密
        byte[][] arrays = spliteArray(bcd, keyLen);
        StringBuilder m = new StringBuilder();
        for(byte[] array : arrays){
            m.append(new String(cipher.doFinal(array)));
        }

        return m.toString();
    }


    /**
     * 生成公钥和私钥
     *
     * @return  map
     */
    public static HashMap<String,Object> getKeys() throws NoSuchAlgorithmException {
        HashMap<String,Object> args = new HashMap<>();

        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(1024);

        KeyPair pair = keyPairGenerator.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) pair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) pair.getPrivate();
        args.put("public",publicKey);
        args.put("private",privateKey);
        return args;
    }


    /**
     * 使用模和指数生成RSA公钥
     *
     *
     * @param modules     模
     *
     * @param exponent  指数
     * @return  返回值
     */
    public static RSAPublicKey getPublicKey(String modules,String exponent){
        try {
            BigInteger b1 = new BigInteger(modules);
            BigInteger b2 = new BigInteger(exponent);

            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(b1,b2);
            return (RSAPublicKey) keyFactory.generatePublic(rsaPublicKeySpec);
        } catch (Exception e) {
            throw new YBException(e.getMessage(),e);
        }
    }


    /**
     * 使用模和只是生成私钥
     * RSA
     *
     * @param modules   模
     * @param exponent  指数
     * @return     私钥
     */
    public static RSAPrivateKey getPrivateKey(String modules,String exponent){
        try{
            BigInteger b1 = new BigInteger(modules);
            BigInteger b2 = new BigInteger(exponent);

            KeyFactory factory = KeyFactory.getInstance("RSA");
            RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(b1,b2);
            return (RSAPrivateKey) factory.generatePrivate(keySpec);
        }catch (Exception e){
            throw new YBException(e.getMessage(),e);
        }
    }


    /**
     * bcd转成字符串
     * 0x开头的数字表示16进制，f表示15的意思
     * 0xf0表示15*16 = 240  10进制
     *
     * @param bytes  byte数组
     * @return  字符串
     */
    public static String bcdToStr(byte[] bytes){
        char temp[] = new char[bytes.length * 2], val;

        for(int i = 0 ; i < bytes.length ; i++){
            val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);
            temp[i * 2] = (char)(val > 9 ? val + 'A' - 10 : val + '0');

            val = (char)(bytes[i] & 0x0f);
            temp[i * 2 + 1] = (char)(val > 9 ? val + 'A' - 10 : val + '0');
        }
        return new String(temp);
    }


    /**
     * ASCII码转 BCD
     *
     * @param ascii  ascii码
     * @param ascLen    len
     * @return   bcd
     */
    public static byte[] ASCII_TO_BCD(byte[] ascii , int ascLen){
        byte[] bcd = new byte[ascLen / 2];
        int j = 0;
        for(int i=0 ; i < (ascLen + 1) / 2 ; i++){
            bcd[i] = asc_to_bcd(ascii[j++]);
            bcd[i] = (byte) (((j >= ascLen) ? 0x00 : asc_to_bcd(ascii[j++])) + (bcd[i] << 4));
        }
        return bcd;
    }


    public static byte asc_to_bcd(byte asc){
        byte bcd;

        if((asc >= '0') && (asc <= '9')){
            bcd = (byte)(asc-'0');
        }else if((asc >= 'A') && (asc <= 'F')){
            bcd = (byte)(asc-'A' + 10);
        }else if((asc >= 'a') && (asc <= 'f')){
            bcd = (byte)(asc - 'a' + 10);
        }else{
            bcd = (byte)(asc - 48);
        }
        return bcd;
    }



    /**
     * 字符串拆分成数组  比如 abcde 拆分成数组为3个单位 为["ab","cd","e"]
     * @param string  被拆分的字符串
     * @param len     拆分段数
     * @return      拆分后的数组
     */
    public static String[] splitString(String string,int len){
        if(len == 0){
            return new String[]{string};
        }

        int x = string.length() / len;
        int y = string.length() % len;
        int z = 0;
        if(y != 0){
            z = 1;
        }
        String[] strings = new String[x+z];
        String str = "";
        int arrayLength = strings.length;
        int stringLen = string.length();

        for(int i=0; i<arrayLength; i++){
            int next = len+(i*len);

            if(i <= x && next <= stringLen){
                str = string.substring(i*len,len+(i*len));
            }else{
                str = string.substring(i*len,string.length());
            }
            strings[i] = str;
        }
        return strings;
    }


    /**
     * 拆分byte一维数组 为二维数组 比如[1,2,3,4,5,6]  拆分2段后为 [[1,2,3],[4,5,6]]
     * @param bytes   被拆分的一维数组
     * @param len    拆分段数
     * @return      拆分后的二维数组
     */
    public static byte[][]  spliteArray(byte[] bytes,int len){
        if(len == 0){
            return new byte[][]{bytes};
        }
        int bytesLen = bytes.length;
        int x = bytesLen / len;
        int y = bytesLen % len;
        int z = 0;

        if(y != 0){
            z = 1;
        }

        byte[][] arrays = new byte[x+z][len];


        for(int i=0 ; i<x+z ; i++){
            byte[] childArr = new byte[len];

            if(i <= x && i*len+len <= bytesLen){
                System.arraycopy(bytes,i*len,childArr,0,len);
            }else{
                System.arraycopy(bytes,i*len,childArr,0,bytesLen-i*len);
            }
            arrays[i] = childArr;
        }
        return arrays;
    }

    public static void main(String[] args) throws Exception {
//        splitString("abcdd",2);
//        spliteArray(new byte[]{'3','4','2','4','6','d','h','s','n','c','4'},2);
//        System.out.println(bcdToStr(new byte[]{'3','4','2','4','6','d','h','s','n','c','4'}));
        HashMap<String,Object> data = getKeys();
        String token = encryptByPublicKey("123");
        String value = decryptByPrivateKey(token);
        System.out.println();
    }
}
